package ru.hackday.wave;

import com.google.wave.api.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class SpeedyServlet extends AbstractRobotServlet {
  private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("ru.hackday.wave.text", Locale.getDefault(), SpeedyServlet.class.getClassLoader());
  private static final Logger logger = Logger.getLogger(SpeedyServlet.class.getName());

  private static final String PREFIX = SpeedyServlet.class.getName() + ".";
  private static final String CONTROL_BLIP = "control";
  private static final String GAME_BLIP = "game";

  public static final String SELF = "speedykbdy@appspot.com";
  private static final String START_BUTTON = "start";
  private static final String TEAM_1_MARKER = "Team 1:";
  private static final String TEAM_2_MARKER = "Team 2:";
  private static final String STYLE_BACKGROUND_COLOR = "style/backgroundColor";

  private static final Comparator<Range> RANGE_COMPARATOR = new RangeComparator();

  private static final String COLOR_BOTH = "rgb(170, 170, 170)";
  private static final String COLOR_T1 = "rgb(200, 130, 130)";
  private static final String COLOR_T2 = "rgb(130, 130, 200)";
  private static final String PUZZLE_ANNOTATION = "game/puzzle";

  private static final String FORMAT = "Team 1: % 3d         Team 2: % 3d";
  private static final String GAME_SCORE = "game/score";
  private static final String GAME_ROUND = "game/round";
  private static final String GAME_T1_WON = "game/t1won";
  private static final String GAME_T2_WON = "game/t2won";

  @Override
  public void processEvents(RobotMessageBundle bundle) {
    logger.info("-> message [" + debugBundle(bundle) + "]");
    Wavelet wavelet = bundle.getWavelet();

    if (bundle.wasSelfAdded()) {
      logger.info(SELF + " was added");
      updateControlBlip(bundle, wavelet);
    }

    for (Event e : bundle.getEvents()) {
      if (e.getType() == EventType.FORM_BUTTON_CLICKED && START_BUTTON.equals(e.getButtonName())) {
        onStartButton(e);
      } else if (e.getType() == EventType.BLIP_SUBMITTED || e.getType() == EventType.BLIP_VERSION_CHANGED) {
        onBlipChange(e);
      }
    }
  }

  private boolean onBlipChange(Event e) {
    Blip blip = e.getBlip();
    if (!isGameStarted(blip)) return false;
    Range puzzleRange = getPuzzleRange(blip);
    if (puzzleRange == null) {
      logger.warning("no puzzle range");
      return false;
    }
    String blipText = blip.getDocument().getText();
    int diff = Math.min(puzzleRange.getEnd(), blipText.length());
    String answers = blipText.substring(diff);
    int t1 = answers.indexOf(TEAM_1_MARKER);
    int t2 = answers.indexOf(TEAM_2_MARKER);
    if (t1 < 0 || t2 < 0) {
      logger.warning("cannot find team markers [" + answers + "]");
      // game ruined?
      return false;
    }
    int ts1 = t1 + TEAM_1_MARKER.length();
    int ts2 = t2 + TEAM_2_MARKER.length();
    String team1text = answers.substring(ts1, t2 > t1 ? t2 : answers.length()).trim();
    String team2text = answers.substring(ts2, t1 > t2 ? t1 : answers.length()).trim();
    logger.info("team 1 : " + team1text);
    logger.info("team 2 : " + team2text);
    TextView doc = blip.getDocument();
    String puzzleText = doc.getText(puzzleRange);
    colorizePussle(blip, team1text, team2text, puzzleRange, puzzleText);

    int round = getRound(doc);
    Range score = getScore(doc);
    List<Integer> t1w = getWonRounds(doc, GAME_T1_WON);
    List<Integer> t2w = getWonRounds(doc, GAME_T2_WON);
    logger.info("round " + round);
    logger.info("score: " + score.getStart() + ":" + score.getEnd());
    logger.info("t1 won: " + t1w);
    logger.info("t2 won: " + t2w);

    if (!t1w.contains(round) && !t2w.contains(round)) {
      logger.info("checking for winner");
      if (puzzleText.trim().equals(team1text.trim())) {
        logger.info("team 1 wins round " + round);
        t1w.add(round);
        score.setStart(score.getStart() + 1);
        newRound(doc, score, round, t1w, t2w);
      } else if (puzzleText.trim().equals(team2text.trim())) {
        logger.info("team 2 wins round " + round);
        t2w.add(round);
        score.setEnd(score.getEnd() + 1);
        newRound(doc, score, round, t1w, t2w);
      }
    } else {

    }

    return true;
  }

  private Range getPuzzleRange(Blip blip) {
    Range puzzleRange;
    List<Annotation> annotations = blip.getDocument().getAnnotations(PUZZLE_ANNOTATION);
    if (annotations.size() == 1) {
      String srange = annotations.get(0).getValue();
      puzzleRange = rangeFromString(srange);
    } else {
      puzzleRange = null;
    }
    return puzzleRange;
  }

  private Range rangeFromString(String srange) {
    try {
      String[] r = srange.split(" ");
      int from = Integer.parseInt(r[0]);
      int to = Integer.parseInt(r[1]);
      return new Range(from, to);
    } catch (NumberFormatException e) {
      return null;
    } catch (IndexOutOfBoundsException e) {
      return null;
    }
  }
  private int intFromString(String s) {
    try {
      return Integer.parseInt(s);
    } catch (NumberFormatException e) {
      return 0;
    }
  }

  private void colorizePussle(Blip blip, String team1text, String team2text, Range range, String puzzleText) {
    TextView doc = blip.getDocument();
    logger.info("puzzle text: " + puzzleText);
    List<Range> team1solved = getSolved(puzzleText, team1text);
    List<Range> team2solved = getSolved(puzzleText, team2text);
    logger.info("team 1 solved: " + team1solved);
    logger.info("team 2 solved: " + team2solved);
    SortedMap<Range, String> colorization = getColorization(team1solved, team2solved);
    logger.info("color: " + colorization);
    List<Annotation> anns = doc.getAnnotations(STYLE_BACKGROUND_COLOR);
    if (!matchAnnotations(range, anns, colorization)) {
      doc.deleteAnnotations(range);
      for (Map.Entry<Range, String> e : colorization.entrySet()) {
        Range r = e.getKey();
        doc.setAnnotation(new Range(range.getStart() + r.getStart(), range.getStart() + r.getEnd()), STYLE_BACKGROUND_COLOR, e.getValue());
      }
    }
  }

  private boolean matchAnnotations(Range range, List<Annotation> anns, SortedMap<Range, String> colorization) {
    // todo optimization
    return false;
  }

  static SortedMap<Range, String> getColorization(List<Range> team1solved, List<Range> team2solved) {
    final Range SENT = new Range(1000000, 1000001);
    ArrayList<Range> t1 = new ArrayList<Range>(team1solved);
    ArrayList<Range> t2 = new ArrayList<Range>(team2solved);
    TreeMap<Range, String> r = new TreeMap<Range, String>(RANGE_COMPARATOR);
    while (!t1.isEmpty() || !t2.isEmpty()) {
      Range r1 = t1.isEmpty() ? SENT : t1.get(0);
      Range r2 = t2.isEmpty() ? SENT : t2.get(0);
      if (r1 != SENT && r1.getStart() < r2.getStart()) {
        int to = Math.min(r1.getEnd(), r2.getStart());
        if (to > r1.getStart()) {
          r.put(new Range(r1.getStart(), to), COLOR_T1);
          cutTo(t1, r1, to);
        }
      } else if (r2 != SENT && r2.getStart() < r1.getStart()) {
        int to = Math.min(r2.getEnd(), r1.getStart());
        if (to > r2.getStart()) {
          r.put(new Range(r2.getStart(), to), COLOR_T2);
          cutTo(t2, r2, to);
        }
      } else {
        assert r2.getStart() == r2.getStart() : r1 + " " + r2;
        int to = Math.min(r2.getEnd(), r1.getEnd());
        r.put(new Range(r2.getStart(), to), COLOR_BOTH);
        cutTo(t2, r2, to);
        cutTo(t1, r1, to);
      }
    }
    return r;
  }

  private static void cutTo(ArrayList<Range> ranges, Range range, int position) {
    assert ranges.get(0) == range;
    if (position >= range.getEnd()) {
      ranges.remove(0);
    } else {
      assert position >= range.getStart() : range + " " + position;
      ranges.set(0, new Range(position, range.getEnd()));
    }
  }

  static List<Range> getSolved(String puzzleText, String solutionText) {
    ArrayList<Range> r = new ArrayList<Range>();
    int s = 0;
    while (puzzleText.length() > 0) {
      int found = -1;
      int p;
      for (p = puzzleText.length(); p > 0; p--) {
        found = solutionText.indexOf(puzzleText.substring(0, p));
        if (found >= 0) {
          break;
        }
      }
      if (found >= 0) {
        r.add(new Range(s, s + p));
        s += p;
        puzzleText = puzzleText.substring(p);
        solutionText = solutionText.substring(found + p);
      } else {
        s++;
        puzzleText = puzzleText.substring(1);
      }
    }
    Collections.sort(r, RANGE_COMPARATOR);
    return r;
  }

  private void onStartButton(Event e) {
    logger.info("starting game");
    Blip childBlip = e.getBlip().createChild();
    TextView doc = childBlip.getDocument();
    newRound(doc);
  }

  private void newRound(TextView doc) {
    Range r = getScore(doc);
    int round = getRound(doc);
    List<Integer> t1w = getWonRounds(doc, GAME_T1_WON);
    List<Integer> t2w = getWonRounds(doc, GAME_T2_WON);
    newRound(doc, r, round, t1w, t2w);
  }

  private void newRound(TextView doc, Range score, int round, List<Integer> t1w, List<Integer> t2w) {
    String s = getTextSeveralTimes();
    if (s == null) {
      logger.warning("cannot get text");
      return;
    }
    doc.delete();
    logger.info("starting round " + (round + 1));
    doc.setAnnotation("game/started", "true");
    doc.setAnnotation(GAME_SCORE, score.getStart() + " " + score.getEnd());
    logger.info("score: " + score.getStart() + ":" + score.getEnd());
    logger.info("t1 won: " + t1w);
    logger.info("t2 won: " + t2w);
    setWonRounds(doc, GAME_T1_WON, t1w);
    setWonRounds(doc, GAME_T2_WON, t2w);
    doc.setAnnotation(GAME_ROUND, String.valueOf(round + 1));

    doc.append(String.format(FORMAT, score.getStart(), score.getEnd()));
    doc.setAnnotation(new Range(0, 6), STYLE_BACKGROUND_COLOR, COLOR_T1);
    doc.setAnnotation(new Range(20, 26), STYLE_BACKGROUND_COLOR, COLOR_T2);
    doc.append("\n\n");
    int start = doc.getText().length();
    s = s.trim();
    String ann = start + " " + (start + s.length());
    doc.append(s);

    appendMarker(doc, TEAM_1_MARKER, COLOR_T1);
    appendMarker(doc, TEAM_2_MARKER, COLOR_T2);

    doc.setAnnotation(PUZZLE_ANNOTATION, ann);
  }

  private void setWonRounds(TextView doc, String ann, List<Integer> rounds) {
    StringBuilder b = new StringBuilder();
    String pref = "";
    for (Integer r : rounds) {
      b.append(pref).append(r);
      pref = " ";
    }
    doc.deleteAnnotations(ann);
    doc.setAnnotation(ann, b.toString());
  }

  private void appendMarker(TextView doc, String marker, String color) {
    doc.append("\n\n");
    int p = doc.getText().length();
    doc.append(marker);
    doc.append(" ");
    doc.setAnnotation(new Range(p, p + marker.length()), STYLE_BACKGROUND_COLOR, color);
  }

  private Range getScore(TextView doc) {
    List<Annotation> list = doc.getAnnotations(GAME_SCORE);
    if (list.isEmpty()) return new Range(0, 0);
    Range r = rangeFromString(list.get(0).getValue());
    return r == null ? new Range(0, 0) : r;
  }

  private int getRound(TextView doc) {
    List<Annotation> list = doc.getAnnotations(GAME_ROUND);
    if (list.isEmpty()) return 0;
    int r = intFromString(list.get(0).getValue());
    return r;
  }

  private List<Integer> getWonRounds(TextView doc, String ann) {
    List<Annotation> list = doc.getAnnotations(GAME_ROUND);
    if (list.isEmpty()) return new ArrayList<Integer>();
    String[] arr = list.get(0).getValue().split(" ");
    ArrayList<Integer> r = new ArrayList<Integer>();
    for (String s : arr) {
      try {
        r.add(Integer.parseInt(s));
      } catch (NumberFormatException e) {
        logger.warning(e.toString());
      }
    }
    return r;
  }

  private boolean isGameStarted(Blip blip) {
    return !blip.getDocument().getAnnotations("game/started").isEmpty();
  }

  private void deleteChildren(Blip blip) {
    List<Blip> children = blip.getChildren();
    for (Blip child : children) {
      deleteChildren(child);
    }
  }

  private String debugBundle(RobotMessageBundle bundle) {
    StringBuilder b = new StringBuilder();
    b.append("w:").append(bundle.getWavelet().getWaveletId());
    for (Event event : bundle.getEvents()) {
      b.append(' ');
      b.append(event.getType());
    }
    return b.toString();
  }

  private void updateControlBlip(RobotMessageBundle bundle, Wavelet wavelet) {
    Blip blip = wavelet.appendBlip();
    TextView textView = blip.getDocument();
    textView.appendMarkup(BUNDLE.getString("welcome.html"));
    textView.appendElement(new FormElement(ElementType.BUTTON, START_BUTTON, BUNDLE.getString(START_BUTTON)));

//    Blip controlBlip = getOrCreateBlip(bundle, wavelet, CONTROL_BLIP);
//    FormElement startButton = getStartButton(controlBlip);
//    boolean gameStarted = getBlip(bundle, wavelet, GAME_BLIP) != null;
//    startButton.setProperty("enabled", gameStarted ? "false" : "true");
  }

/*
  private FormElement getStartButton(Blip controlBlip) {
    TextView textView = controlBlip.getDocument();
    FormElement startButton;
    List<Element> buttons = textView.getElements(ElementType.BUTTON);
    if (buttons.size() == 1) {
      startButton = (FormElement) buttons.get(0);
    } else {
//      textView.delete();
      textView.append(BUNDLE.getString("welcome.html"));
      startButton = new FormElement(ElementType.BUTTON, START_BUTTON, BUNDLE.getString(START_BUTTON));
      textView.appendElement(startButton);
    }
    return startButton;
  }
*/

/*
  private Blip getOrCreateBlip(RobotMessageBundle bundle, Wavelet wavelet, String blipKey) {
    Blip blip = getBlip(bundle, wavelet, blipKey);
    if (blip == null) {
      blip = wavelet.appendBlip(PREFIX + blipKey);
      logger.info("appended blip " + blip.getBlipId());
//      wavelet.setDataDocument(PREFIX + blipKey, blip.getBlipId());
    }
    return blip;
  }
*/

/*
  private Blip getBlip(RobotMessageBundle bundle, Wavelet wavelet, String blipKey) {
    String blipId = wavelet.getDataDocument(PREFIX + blipKey);
    logger.info("data document: " + PREFIX + blipKey + " => " + blipId);
    Blip blip = null;
    if (blipId != null) {
      blip = bundle.getBlip(wavelet.getWaveId(), wavelet.getWaveletId(), blipId);
      logger.info("search for blip: " + blipId + " => " + (blip == null ? "null" : blip.getDocument().getText()));
    }
    return blip;
  }
*/

  private void stopGame(RobotMessageBundle bundle, Wavelet wavelet) {
    // todo
  }


  private static String getText() throws IOException {
    URL url = new URL("http://zen.yandex.ru/yandsearch?random=" + new Date().getTime() + "&randomtext=1&lr=10174");
    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
    StringBuilder b = new StringBuilder(50000);
    try {
      String line;
      while ((line = reader.readLine()) != null) {
        b.append(line);
      }
    } finally {
      reader.close();
    }

    String doc = b.toString();
    String start = "<div class=\"kk\">";
    String finish = "</div>";
    int s = doc.indexOf(start);
    if (s < 0) return null;
    int f = doc.indexOf(finish, s + start.length());
    if (f < 0) return null;
    String cut = new String(doc.substring(s + start.length(), f));
    if (cut.length() < 5) return null;

    StringBuilder finalText = new StringBuilder(cut.length());
    char lastChar = 0;
    boolean inTag = false;
    for (int i = 0; i < cut.length(); i++) {
      char c = cut.charAt(i);
      if (inTag) {
        if (c == '>') {
          inTag = false;
        }
        continue;
      }
      if (c == '<') {
        inTag = true;
        continue;
      }
      if (Character.isWhitespace(c)) {
        if (lastChar != 0 && lastChar != ' ') {
          finalText.append(' ');
          lastChar = ' ';
        }
        continue;
      }
      if (Character.isLetterOrDigit(c) || c == '.' || c == ',' || c == '?' || c == '!' || c == '+' || c == '-' || c == '(' || c == ')' || c == '"' || c == '\'' || c == ':' || c == ';') {
        finalText.append(c);
        lastChar = c;
      }
    }

    String r = finalText.toString();
    return r.length() < 5 ? null : r;
  }

  public String getTextSeveralTimes() {
    for (int i = 0; i < 5; i++) {
      try {
        String t = getText();
        if (t != null) return t;
      } catch (IOException e) {
        logger.log(Level.WARNING, "cannot get text", e);
      }
    }
    return null;
  }

  public static void main(String[] args) throws IOException, InterruptedException {

    for (int i = 0; i < 100; i++) {
      String t = getText();
      System.out.println(i + ": " + t);
      Thread.sleep(3000);
    }
  }

  private static class RangeComparator implements Comparator<Range> {
    @Override
    public int compare(Range o1, Range o2) {
      if (o1.equals(o2)) return 0;
      if (o1.getStart() == o2.getStart()) return o1.getEnd() < o2.getEnd() ? -1 : 1;
      return o1.getStart() < o2.getStart() ? -1 : 1;
    }
  }
}
